package com.download.download;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;

import com.download.entity.DownloadInfo;
import com.download.exception.DownloadException;
import com.download.utils.ConvertUtil;
import com.download.utils.HttpUtil;
import com.download.utils.LogUtil;

/**
 * @author home
 *
 */
public class MulThreadDownload implements Runnable {

    //下载信息
    private DownloadInfo downloadInfo;
    //起始点组
    private long[] startPositions;
    //结束点组
    private long[] endPositions;
    //未下载完起始点组
    private long[] unfinishedStartPositions;
    //未下载完结束点组
    private long[] unfinishedEndPositions;
    //未完成下载部分起止点列表
    private List<Long> unfinishedStartPositionList = new ArrayList<Long>();
    private List<Long> unfinishedEndPositionList = new ArrayList<Long>();
    //数组长度 
    private int length;
    // 文件长度
    private int fileLength;
    //文件片段长度 
    private int blockLength;
    //线程组
    private SingleThreadDownload[] singleThreadDownload;
    //保存下载信息的文件
    private File positionFile;
    //下载信息文件后缀名
    private String positionSuffix = ".pos";
    // 是否停止下载
    private boolean isStop = false;
    //是否是第一次下载
    private boolean isFirst = true;
    //是否完成下载
    private boolean isFinished = false;

    public MulThreadDownload(DownloadInfo downloadInfo) {
        this.downloadInfo = downloadInfo;
        this.length = downloadInfo.getSplitter();
        this.positionFile = new File(downloadInfo.getFilePath()+File.separator+downloadInfo.getFileName() + positionSuffix);
        File file = new File(downloadInfo.getFilePath());
        if(!file.exists()){ //下载文件存放目录不存在创建目录
            file.mkdirs();
        }
        if (positionFile.exists()) {
            try {
                isFirst = false;
                readPositonInfo();
            } catch (Exception e) {
                throw new DownloadException("MulThreadDownload.constructor", e);
            }
        }else{
            this.startPositions = new long[length];
            this.endPositions = new long[length];
        }
    }

    @Override
    public void run() {
        try {
            if (HttpUtil.getResponseCode(downloadInfo.getUrl(),false) == 200) { //校验连接是否正常
                if(HttpUtil.getResponseCode(downloadInfo.getUrl(),true) == 200) { //当文件不支持多线程下载时即请求头包含Range但响应状态码仍为200时 
                    length = 1;
                    downloadInfo.setSplitter(length);
                }
                if (isFirst) { 
                    //根据文件字节数初始化位置信息数组
                    fetchFileLength();
                    initPositionInfo();
                } else { 
                    //若非第一次下载，且上次未完成下载，则将下载状态设置为未完成
                    checkUnfinishedPositions();
                }
                LogUtil.info("【" + downloadInfo.getFileName() + "】" + ConvertUtil.formatFloat((float)fileLength/(1024*1024),"##.##") +"MB");
                if (!isFinished) { 
                    initDownloadThread();
                    writePositonInfo();
                } else {
                    LogUtil.info("文件已下载！");
                }
            } else { 
                LogUtil.info("未能成功下载，响应信息如下：");
                //http连接异常，打印响应信息
                HttpUtil.printResponseInfo(HttpUtil.getHttpConnection(downloadInfo.getUrl()));
            }
        } catch (Exception e) {
                throw new DownloadException("MulThreadDownload.run()", e);
        }
    }

    /**
     * 记录下载信息直到下载完成
     * @throws IOException 
     * @throws InterruptedException 
     */
    public void writePositonInfo() throws IOException, InterruptedException {
        //写入下载信息,并记录下载进度,直到下载完成
        while (!isStop) {
            //假设下载完毕
            isStop = true; 
            //验证是否下载完毕，逐一判断每个线程是否下载完成，只要有一个线程没完成，继续记录下载信息。
            for (int i = 0; i < length; i++) { 
                if (!singleThreadDownload[i].isFinished()) { 
                    isStop = false;
                    break;
                }
            }
            doWritePositonInfo();
            LogUtil.info("【" + downloadInfo.getFileName() + "】" + "下载进度："+ConvertUtil.float2Percent(getDownloadProgress(),2));
            //当前执行线程休眠1s，以便启动下载线程
            Thread.sleep(1000); 
        }
        //下载完成删除位置文件
        deletePosFiles();
    }

    /**
     * 将下载信息依次写入pos文件，这些信息包括：
     * 1、文件长度（fileLength）
     * 2、计算出的文件片段大小（blockLength）
     * 3、文件片段数（length）
     * 4、每个线程下载起始点（startPosition）
     * 5、每个线程下载截止点（endPosition）
     * 下载信息串格式：
     * fileLength,blockLength,length, startPostion|endPosition#startPostion|endPosition#startPostion|endPosition#startPostion|endPosition#startPostion|endPosition
     * @throws IOException 
     */
    public void doWritePositonInfo() throws IOException  {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(positionFile));
        int splitter = downloadInfo.getSplitter(); //表示线程数
        StringBuffer positionInfo = new StringBuffer();
        positionInfo.append(fileLength + ",");
        positionInfo.append(blockLength + ",");
        positionInfo.append(splitter + ",");
        String positions = "";
        int count = 0; //记录未完成下载的线程数
        for (int i = 0; i < splitter; i++) {
            if(splitter == length) { //首次下载
                positions += singleThreadDownload[i].getStartPosition() + "-";
            } else if (splitter > length) { //设定线程数 splitter > length，说明是部分下载完成
                if(startPositions[i] -  endPositions[i] == 1){ //已下载部分从起始点组中取值
                    positions += startPositions[i] + "-";
                } else { //未下载部分处理 需要未完成部分在数组中的索引
                    count++; //记录已经找到在起止点数组插入位置的次数
                    if(count <= length){
                        positions += singleThreadDownload[count-1].getStartPosition() + "-";
                    }
                }
            }
            positions += endPositions[i] + "#"; //endPostions分配完毕，不会再被修改
            //LogUtil.log("startPosition["+ i +"]="+singleThreadDownload[i].getStartPosition()+";endPosition["+ i +"]="+singleThreadDownload[i].getEndPosition());
        }
        positions = positions.substring(0, positions.length()-1);
        positionInfo.append(positions);
        dos.writeUTF(positionInfo.toString());
        dos.flush();
        dos.close();
    }

    /**
     * 获取下载进度
     */
    public float getDownloadProgress() {
        //计算下载进度
        long hasDownloadSize = 0L;
        float percent = 0f;
        int splitter = downloadInfo.getSplitter();
        int count = 0;
        for (int i = 0; i < splitter; i++) {
            if(splitter == length) { //首次下载
                hasDownloadSize  += singleThreadDownload[i].getStartPosition() - i * blockLength;
            } else if (splitter > length) { //设定线程数 splitter > length，说明是部分下载完成
                if(startPositions[i] -  endPositions[i] == 1){ //已下载部分从起始点组中取值
                    hasDownloadSize += startPositions[i] - i * blockLength;
                } else { //未下载部分处理 需要未完成部分在数组中的索引
                    count++; //
                    if(count <= length){
                        hasDownloadSize += singleThreadDownload[count-1].getStartPosition() - i * blockLength;
                    }
                }
            }
        }
        percent = (float) hasDownloadSize / fileLength;
        return percent;
    }
    
    /**
     * 读取已下载文件的信息
     * @throws IOException 
     */
    public void readPositonInfo() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream(positionFile));
        String[] positionInfo = dis.readUTF().split(",");
        fileLength = Integer.valueOf(positionInfo[0]);
        blockLength = Integer.valueOf(positionInfo[1]);
        length =  Integer.valueOf(positionInfo[2]);//读取线程数
        String positions = positionInfo[3];
        String[] temp = positions.split("#");
        startPositions = new long[length];
        endPositions = new long[length];
        for (int i = 0; i < length; i++) {
          startPositions[i] = Long.valueOf(temp[i].split("-")[0]);;
          endPositions[i] =Long.valueOf(temp[i].split("-")[1]);
        }
        LogUtil.info("读取下载起止点组：startPositions=" + ConvertUtil.longArray2String(startPositions) +";endPositions="+ ConvertUtil.longArray2String(endPositions));
        dis.close();
    }
    
    /**
     * 初始化startPositions和endPositions。依据设定的length将文件分成若干片段。如文件大小为11B，分为5个片段，每段大小则为11/5 = 2。
     * 因文件为11B，所以将前4段按照每段2字节分段，最后一段3字节，以保证文件每个字节都包含到。
     * @param fileLength
     */
    public void initPositionInfo() {
        blockLength = fileLength / length;
        for (int i = 0 ; i < length ; i++) {
            startPositions[i] = i * blockLength;
            if (i==length-1) {
                endPositions[i] = fileLength -1;
            } else { 
                endPositions[i] = (i+1) * blockLength - 1;
            }
        }
        unfinishedStartPositions = startPositions;
        unfinishedEndPositions = endPositions;
    }
    
    /**
     * 创建线程组，启动单线程下载
     * @throws InterruptedException 
     * @throws IOException 
     */
    public void initDownloadThread() throws InterruptedException, IOException{
        singleThreadDownload = new SingleThreadDownload[length];
        for (int i = 0 ; i < length ; i++) {
            singleThreadDownload[i] = new SingleThreadDownload(i, downloadInfo.getUrl(),unfinishedStartPositions[i], unfinishedEndPositions[i], downloadInfo.getFilePath() + File.separator + downloadInfo.getFileName());
            new Thread(singleThreadDownload[i]).start();
            Thread.sleep(2000); //启动子线程需要时间 故将当前执行线程休眠2s
            LogUtil.info("[线程" + i + "]：【" + downloadInfo.getFileName() + "】的字节范围["+unfinishedStartPositions[i]+"-"+unfinishedEndPositions[i]+"]");
        }
    }
    
    /**
     * 筛选未完全下载部分的起止位置
     */
    public void checkUnfinishedPositions() { 
        for (int i = 0; i < length; i++) { //判断下载完毕的唯一条件是 startPosition 比 endPosition 大1
            isFinished = (startPositions[i] - endPositions[i] == 1);
            //如何筛选出未完全下载的部分，并只下载这些文件片段？ 记录数组中索引为i的值，记录i的个数，并赋值给length
            if(!isFinished){
                unfinishedStartPositionList.add(startPositions[i]);
                unfinishedEndPositionList.add(endPositions[i]);
            }
        }
        length = unfinishedStartPositionList.size(); // length = 0，下载完毕，isFinishe必然为true
        if(length > 0){ // length > 0，下载未完成，isFinishe置为false
            unfinishedStartPositions =ConvertUtil.convertLong2long(unfinishedStartPositionList);
            unfinishedEndPositions = ConvertUtil.convertLong2long(unfinishedEndPositionList);
            isFinished = false;
        }
    }
    
    /**
     * 取得文件大小
     * @return
     * @throws IOException 
     */
    public void fetchFileLength() throws IOException {
        HttpURLConnection conn = HttpUtil.getHttpConnection(downloadInfo.getUrl());
        if (conn.getResponseCode() == 200) {
            fileLength = conn.getContentLength();
        } else {
            LogUtil.info("获取文件长度出错，响应信息如下：");
            //连接异常打印响应信息
            HttpUtil.printResponseInfo(conn);
        }
        //断开连接
        HttpUtil.disconnect(conn);
    }
    
    /**
     * 删除position文件
     */
    public void deletePosFiles() {
        File dir = new File(downloadInfo.getFilePath());
        File[] files = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.equals(downloadInfo.getFileName() + ".pos");
            }
        });
        for (File file : files) {
            file.delete();
        }
        LogUtil.info("文件删除成功");
    }
    
}